/*
* INSANE - Interactive Structural Analysis Environment
*
* Copyright (C) 2003-2004
* Universidade Federal de Minas Gerais
* Escola de Engenharia
* Departamento de Engenharia de Estruturas
*
* Author's email : insane@dees.ufmg.br
* Author's Website : http://www.dees.ufmg.br/insane
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
*/
package br.ufmg.dees.insane.util.xfem;

import br.ufmg.dees.insane.util.IMatrix;
import br.ufmg.dees.insane.util.IPoint3d;
import br.ufmg.dees.insane.util.IVector;

/**
 * @author Kelson Wolff
 *
 */
public class Segment implements java.io.Serializable{
	
	private static final long serialVersionUID = 1L;
	
	/**
	 * The first point of this segment
	 */
	protected IPoint3d firstPoint;
	
	/**
	 * The second point of this segment
	 */
	protected IPoint3d secondPoint;
	
	/**
	 * The vector of orientation associated with this Segment
	 */
	protected IPoint3d vector;
	
	/**
	 * The numeric limit used for tolerance computation
	 */
	protected final double epsilon = 1e-6;

	/**
	 * The constructor of this Line with parameters.
	 * @param firstPoint The first point of this Segment.
	 * @param secondPoint The second point of this Segment.
	 */
	public Segment(IPoint3d firstPoint, IPoint3d secondPoint) {
		this.firstPoint = firstPoint;
		this.secondPoint = secondPoint;
		double x = this.secondPoint.getX()-this.firstPoint.getX();
		double y = this.secondPoint.getY()-this.firstPoint.getY();
		double z = this.secondPoint.getZ()-this.firstPoint.getZ();
		this.vector = new IPoint3d(x,y,z);
	}
	
	/**
	 * The constructor of this Segment without parameters.
	 */
	public Segment() {
		this.firstPoint = new IPoint3d(0.0,0.0,0.0);
		this.secondPoint = new IPoint3d(0.0,0.0,0.0);
		this.vector = new IPoint3d(0.0,0.0,0.0);
	}

	/**
	 * @return Returns the firstPoint.
	 */
	public IPoint3d getFirstPoint() {
		return firstPoint;
	}

	/**
	 * @param firstPoint The firstPoint to set.
	 */
	public void setFirstPoint(IPoint3d firstPoint) {
		this.firstPoint = firstPoint;
	}

	/**
	 * @return Returns the secondPoint.
	 */
	public IPoint3d getSecondPoint() {
		return secondPoint;
	}

	/**
	 * @param secondPoint The secondPoint to set.
	 */
	public void setSecondPoint(IPoint3d secondPoint) {
		this.secondPoint = secondPoint;
	}

	/**
	 * @return Returns the vector.
	 */
	public IPoint3d getVector() {
		return vector;
	}

	/**
	 * @param vector The vector to set.
	 */
	public void setVector(IPoint3d vector) {
		this.vector = vector;
	}
	
	/** Returns True if there is intersection between provided Line l and
	 * this Segment or False if there isn't intersection.
	 * @param l The provided Line for test intersection with this Segment.
	 * @return True if there is intersection between provided Line l and
	 * this Segment or False if there isn't intersection.
	 */
	public boolean intersects(Line l){
		double t = 0;
		double t0 = 0;
		
		double vecx = this.getVector().getX();
		double vecy = this.getVector().getY();
		double lvx = l.getVector().getX();
		double lvy = l.getVector().getY();
		double lox = l.getOrigin().getX();
		double loy = l.getOrigin().getY();
		double fx = this.getFirstPoint().getX();
		double fy = this.getFirstPoint().getY();
		
		if(vecx * lvy - vecy * lvx == 0)
			return false;
		else{
			if(vecx != 0 && vecy != 0){
				if(lvy != 0 && lvx != 0){
					t = (loy + (fx - lox) / lvx * lvy) / (vecy + vecx/ lvx * lvy);
				}
				else if(lvy == 0){
					t = (loy - fy) / vecy;
				}
				else if(lvx == 0){
					t = (lox - fx) / vecx;
				}
			}
			else if(vecx == 0){
				t0 = (fx - lox) / lvx;
				t = (loy + t0* loy - fy) / vecy;
			}
			else if(vecy == 0){
				t0 = (fy - loy) / lvy;
				t = (lox + t0* lvx - fx) / vecx;
			}
			return (t > 0 && t < 1);
			
//			return std::abs((l->vector()->y*vec.x - l->vector()->x*vec.y)/(l->vector()->norm()*vec.norm())) < 1 ;   ????????
		}	
	}
	
	/** Returns True if there is intersection between provided Segment s and
	 * this Segment or False if there isn't intersection.
	 * @param s The provided Segment for test intersection with this Segment.
	 * @return True if there is intersection between provided Segment s and
	 * this Segment or False if there isn't intersection.
	 */
	public boolean intersects(Segment s){
		if((this.getVector().getX() * s.getVector().getY()) - (s.getVector().getX() * this.getVector().getY()) == 0){
			return false;
		}
		else{
			IMatrix m = new IMatrix(2,2);
			IVector v = new IVector(2);
			
			m.setElement(0,0,this.getVector().getX());
			m.setElement(0,1,-s.getVector().getX());
			m.setElement(1,0,this.getVector().getY());
			m.setElement(1,1,-s.getVector().getY());
			v.setElement(0,s.getFirstPoint().getX() - this.getFirstPoint().getX());
			v.setElement(1,s.getFirstPoint().getY() - this.getFirstPoint().getY());
			
			IVector fac = m.inverse().mul(v);
			return (((fac.getElement(0)<1 && fac.getElement(0)>0) && (fac.getElement(1)<1 && fac.getElement(1)>0)) ? true:false);
		}
	}
	
	/** Returns True if the provided point p is on this Segment or False if
	 * it isn't.
	 * @param p The provided point for positioning test with this Segment.
	 * @return True if the provided point p is on this Segment or False if
	 * it isn't.
	 */
	public boolean on(IPoint3d p){
		boolean result = false;
		if(this.getVector().getX() == 0 && this.getVector().getY() == 0)
			result = false;
		if(!(this.isAligned(p,this.getFirstPoint(),this.getSecondPoint()))){
			result = false;
		}	
		else if(Math.abs(this.getVector().getX()) > Math.abs(this.getVector().getY())){
			double t = (p.getX() - this.getFirstPoint().getX()) / this.getVector().getX();
			result = (((t > (-0.000000000000001)) && (t < (1.0 + 0.000000000000001))));
		}
		else{
			double t = (p.getY() - this.getFirstPoint().getY()) / this.getVector().getY();
			result = (((t > (-0.000000000000001)) && (t < (1.0 + 0.000000000000001))));
		}
		return result;
	}
	
	/** Returns the intersection point between provided Line l and
	 * this Segment if exists intersection between them.
	 * @param s The provided Line that intersects this Segment.
	 * @return The intersection point between provided Line l and
	 * this Segment if exists intersection between them.
	 */
	public IPoint3d intersection(Line l){
		IMatrix m = new IMatrix(2,2);
		IVector v = new IVector(2);
		
		m.setElement(0,0,this.getVector().getX());
		m.setElement(0,1,-l.getVector().getX());
		m.setElement(1,0,this.getVector().getY());
		m.setElement(1,1,-l.getVector().getY());
		
		v.setElement(0,l.getOrigin().getX() - this.getFirstPoint().getX());
		v.setElement(1,l.getOrigin().getY() - this.getFirstPoint().getY());
		
		IVector fac = (m.inverse()).mul(v);
		
		double x = this.getFirstPoint().getX() + this.getVector().getX() * fac.getElement(0);
		double y = this.getFirstPoint().getY() + this.getVector().getY() * fac.getElement(0);
		IPoint3d intersection = new IPoint3d(x,y);
		
		return intersection;
	}
	
	/** Returns the intersection point between provided Segment s and
	 * this Segment if exists intersection between them.
	 * @param s The provided Segment that intersects this Segment.
	 * @return The intersection point between provided Segment s and
	 * this Segment if exists intersection between them.
	 */
	public IPoint3d intersection(Segment s){
		if(this.intersects(s)){
			IMatrix m = new IMatrix(2,2);
			IVector v = new IVector(2);
			
			m.setElement(0,0,this.getVector().getX());
			m.setElement(0,1,-s.getVector().getX());
			m.setElement(1,0,this.getVector().getY());
			m.setElement(1,1,-s.getVector().getY());
			v.setElement(0,s.getFirstPoint().getX() - this.getFirstPoint().getX());
			v.setElement(1,s.getFirstPoint().getY() - this.getFirstPoint().getY());
			
			IVector fac = m.inverse().mul(v);
			
			double x = this.getFirstPoint().getX() + fac.getElement(0)*this.getVector().getX();
			double y = this.getFirstPoint().getY() + fac.getElement(0)*this.getVector().getY();
			IPoint3d iPoint = new IPoint3d(x,y);
			return iPoint;
		}
		else{
			System.out.println("There is no intersection between segments!!!");
			return null;
		}
	}
	
	/**
	 * Returns True if the provided IPoint3d test is aligned with segment 
	 * formed by IPoint3d p0 and IPoint3d p1 and False if it isn't.
	 * @param test The provided point for alignment test.
	 * @param p0 The first point of provided segment.
	 * @param p1 The second point of provided segment.
	 * @return True if the provided IPoint3d test is aligned with segment 
	 * formed by IPoint3d p0 and IPoint3d p1 and False if it isn't.
	 */
	public boolean isAligned(IPoint3d test, IPoint3d p0, IPoint3d p1){
		return((Math.abs(((p1.getX()-test.getX())*(p0.getY()-test.getY()))-((p0.getX()-test.getX())*(p1.getY()-test.getY()))) < 10.0*this.epsilon) ? true:false);
	}
	
	/**
	 * Returns true if this Segment is coincidental with provided
	 * Segment s and false if it isn't.
	 * @param s The provided Segment for coincidence test with this Segment.
	 * @return True if this Segment is coincidental with provided
	 * Segment s and false if it isn't.
	 */
	public boolean isCoincidental(Segment s){
		double tFX = this.getFirstPoint().getX();
		double tFY = this.getFirstPoint().getY();
		double tSX = this.getSecondPoint().getX();
		double tSY = this.getSecondPoint().getY();
		double sFX = s.getFirstPoint().getX();
		double sFY = s.getFirstPoint().getY();
		double sSX = s.getSecondPoint().getX();
		double sSY = s.getSecondPoint().getY();
		return((((tFX == sFX && tFY == sFY) && (tSX == sSX && tSY == sSY)) || ((tFX == sSX && tFY == sSY) && (tSX == sFX && tSY == sFY))) ? true:false );
	}

}
